﻿using Caliburn.Micro;
using MusicExplorer.Common;
using MusicExplorer.Data;
using MusicExplorer.Model;
using MusicExplorer.Repository;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.System.Threading;
using Windows.UI.Core;
using Windows.UI.Xaml.Data;
using MusicExplorer.Extensions;

namespace MusicExplorer.ViewModels.Wrappers
{
    public sealed class TracksCollection : BindableCollection<TrackTileViewModel>, ISupportIncrementalLoading
    {
        private const int TRACKS_TO_ADD = 20;
        private const int MAX_CONCURRENCY = 5;

        private sealed class Query
        {
            private readonly List<Track> m_allTracks;
            private readonly List<Track> m_basketTracks;
            private readonly RepositoryBase m_repository;
            private readonly Entity m_entity;

            private int m_currentPage;

            public Query(Entity entity)
            {
                m_allTracks = new List<Track>();
                m_basketTracks = new List<Track>();
                m_entity = entity;
                m_repository = RepositoryBase.GetInstance(entity.Repository);
                m_currentPage = m_repository.StartingPage;
                m_hasMoreItem = true;
            }

            public async Task LoadNextTracksAsync(QueryType type, CancellationToken token)
            {
                try
                {
                    token.ThrowIfCancellationRequested();
                    var tracks = await m_repository.GetTracksAsync(type, m_entity, m_currentPage, token);
                    if (tracks == null || (tracks != null && tracks.Count == 0))
                    {
                        m_hasMoreItem = false;
                    }
                    else
                    {
                        m_allTracks.AddRange(tracks);
                        m_basketTracks.AddRange(tracks);
                        m_currentPage++;
                    }
                }
                catch (Exception ex)
                {
                    m_hasMoreItem = false;
                }
            }

            private bool m_hasMoreItem;
            public bool HasMoreItems { get { return m_hasMoreItem; } }
            public bool LoadingNeeded { get { return m_basketTracks.Count < TRACKS_TO_ADD && m_hasMoreItem; } }
            public List<Track> AllTracks { get { return m_allTracks; } }
            public List<Track> BasketTracks { get { return m_basketTracks; } }
            public Model.Repository Repository { get { return m_entity.Repository; } }
        }

        private bool m_firstTime;

        private readonly QueryGroup m_group;
        private readonly PageViewModelBase m_parent;
        private readonly CancellationToken m_token;
        private readonly List<Query> m_queries;
        private readonly CoreWindow m_window;

        private Dictionary<Guid, IEnumerable<Query>> GetQueriesByRepositories(IEnumerable<Query> queries)
        {
            return queries.GroupBy(e => e.Repository.Id).ToDictionary(g => g.Key, g => g.AsEnumerable());
        }
        private IEnumerable<Track> GetTracksByRepository(Model.Repository repository)
        {
            return this.Select(t => t.Track).Where(t => t.Repository.Id == repository.Id);
        }
        private bool LoadingNeeded { get { return m_queries.Exists(q => q.LoadingNeeded); } }
        public bool HasMoreItems { get { return m_firstTime || m_queries.Exists(q => q.HasMoreItems); } }

        public TracksCollection(PageViewModelBase parent, QueryGroup group, CancellationToken token)
        {
            m_firstTime = true;
            m_parent = parent;
            m_group = group;
            m_token = token;
            m_queries = new List<Query>();
            m_window = CoreWindow.GetForCurrentThread();
        }

        private void InitEntities()
        {
            for(var i = 0; i < m_group.Entities.Count; i++)
            {
                var entity = m_group.Entities[i];
                m_queries.Add(new Query(entity));
            }
        }

        public event EventHandler StartSearching = delegate { };
        public event EventHandler EndSearching = delegate { };
        public Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint page)
        {
            StartSearching(this, EventArgs.Empty);

            return Task.Run<LoadMoreItemsResult>(async () =>
                {
                    if (Parameters.UseSampleData)
                    {
                        await m_window.Dispatcher.ExecuteInUIThread(async () =>
                            {
                                m_firstTime = false;
                                var repo = RepositoryBase.GetRepository(new Guid("C0C5501F-D815-41AF-A3CB-A8163D5D8E5A"));
                                for (int i = 0; i < 50; i++)
                                {
                                    var track = new Track(i.ToString(), repo, TrackType.Classic);
                                    track.IsRead = i % 4 == 0;
                                    track.Name = "Track Name " + i + " prFAxJHGJFDfjdkljjjll  ppazhod uhazo haozudh oazudh oauzdh oauzdh oazuhd ozau hdoauzh douahz douazh oduazhoduh aozduh ";
                                    track.PublishDate = DateTime.Now;
                                    track.Artists[ArtistRole.Composer] = new List<Artist>();
                                    track.Artists[ArtistRole.Composer].Add(new Artist("123", "ARTIST 1", repo));
                                    track.Artists[ArtistRole.Remixer] = new List<Artist>();
                                    track.Artists[ArtistRole.Remixer].Add(new Artist("789", "ARTIST 3", repo));
                                    track.Artists[ArtistRole.Remixer].Add(new Artist("456", "ARTIST 2", repo));
                                    track.Artwork = @"http://placekitten.com/50/50";
                                    track.Mp3 = @"http://8thwondercdpool.com/8thwondercdpool_sample.mp3";
                                    var index = i;
                                    var wrapper = await TrackTileViewModel.CreateAsync(m_parent, track, index);
                                    Add(wrapper);
                                }
                            });

                        return new LoadMoreItemsResult()
                        {
                            Count = 50
                        };
                    }

                    if (m_firstTime)
                    {
                        InitEntities();
                        m_firstTime = false;
                    }

                    do
                    {
                        var entitiesToLoad = m_queries.Where(e => e.LoadingNeeded).ToArray();
                        int loop = entitiesToLoad.Length / MAX_CONCURRENCY + 1;
                        for (int i = 0; i < loop; i++)
                        {
                            int max = i * MAX_CONCURRENCY + MAX_CONCURRENCY;
                            int size = MAX_CONCURRENCY;
                            if (entitiesToLoad.Length < max)
                                size = entitiesToLoad.Length - (max - MAX_CONCURRENCY);

                            Task[] tasks = new Task[size];
                            for (int j = 0; j < size; j++)
                            {
                                if (entitiesToLoad.Length <= j) continue;

                                var entity = entitiesToLoad[j + i * MAX_CONCURRENCY];
                                tasks[j] = Task.Run(() => entity.LoadNextTracksAsync(m_group.QueryType, m_token));
                            }

                            try
                            {
                                Task.WaitAll(tasks);
                            }
                            catch (Exception ex) { }
                        }

                        // For each repositories
                        foreach(var repository in GetQueriesByRepositories(m_queries))
                        {
                            // Get tracks already added for current repository
                            var existingTracksByRepo = GetTracksByRepository(repository.Value.First().Repository);

                            // For each query in current repository
                            foreach(var query in repository.Value)
                            {
                                // Get track ids in basket but already added and remove
                                var trackIds = query.BasketTracks.Select(t => t.Id).Where(id => existingTracksByRepo.Select(t => t.Id).Contains(id));
                                query.BasketTracks.RemoveAll(t => trackIds.Contains(t.Id));
                            }

                            // Get all tracks in basket for current repository which exist in more that one basket
                            var allBasketTracks = repository.Value.SelectMany(e => e.BasketTracks);
                            var grouppedBasketTracks = from t in allBasketTracks
                                                       group t by t.Id into gr
                                                       where gr.Count() > 1
                                                       select gr;

                            // For each of these tracks
                            foreach(var tracks in grouppedBasketTracks)
                            {
                                // Get first track (they are all the same)
                                var track = tracks.First();

                                // Get all queries which contains this track
                                var queryToProcess = repository.Value.Where(q => q.BasketTracks.Exists(t => t.Id == track.Id));

                                // Remove this tracks in all queries except one
                                foreach (var query in queryToProcess)
                                {
                                    if(query != queryToProcess.Last())
                                    {
                                        var index = query.BasketTracks.FindIndex(t => t.Id == track.Id);
                                        query.BasketTracks.RemoveAt(index);
                                    }
                                }
                            }
                        }

                    } while (LoadingNeeded);

                    IEnumerable<Track> tracksToAdd;
                    switch(m_group.QueryType)
                    {
                        case QueryType.AllTracks:
                            tracksToAdd = m_queries.SelectMany(t => t.BasketTracks).OrderByDescending(t => t.PublishDate).Take(TRACKS_TO_ADD);
                            break;
                        case QueryType.TopTracks:
                            tracksToAdd = m_queries.SelectMany(t => t.BasketTracks).OrderBy(t => t.Position).Take(20);
                            break;
                        default:
                            throw new Exception("Not supported query type");
                    }
                    var areTracksRead = await ServiceDatabase.AreTracksRead(tracksToAdd);

                    await m_window.Dispatcher.ExecuteInUIThread(async () =>
                    {
                        foreach (var track in tracksToAdd)
                        {
                            var closure = track;
                            var entityToDeleteIn = m_queries.First(e => e.BasketTracks.Any(t => t.Id == closure.Id && t.Repository.Id == closure.Repository.Id));
                            entityToDeleteIn.BasketTracks.Remove(closure);

                            if (areTracksRead[new Tuple<Guid, string>(track.Repository.Id, track.Id)]) closure.IsRead = true;
                            this.Add(await TrackTileViewModel.CreateAsync(m_parent, closure, Count - 1));
                        }
                    });
                    
                    await m_window.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => EndSearching(this, EventArgs.Empty));
                    return new LoadMoreItemsResult()
                    {
                        Count = TRACKS_TO_ADD
                    };
                }).AsAsyncOperation<LoadMoreItemsResult>();
        }
    }
}
